local uv = require('luv')
local json = require('dkjson')

-- arg[1], project path
-- arg[2], language id

local pathToLspFiles = "/tmp/"
local projectRoot = arg[1]
local langId = arg[2]

local function pickLsp(lid)
  switch = {
    ["c"] = function()
      return "ccls", {'-log-file=/tmp/ccls2.log', '-init={}'}
    end,
    ["h"] = function()
      return "ccls", {'-log-file=/tmp/ccls2.log', '-init={}'}
    end,
    ["hs"] = function()
      return "haskell-language-server-9.6.5", {"--lsp"}
    end,
    ["kt"] = function()
      return "kotlin-language-server", {}
    end,
    ["default"] = function()
      return nil, nil
    end
  }

  local v, a = (switch[lid] or switch["default"])()

  return v, a
end

local lspCmd, lspArgs = pickLsp(langId)

if lspCmd == nil then
  os.exit(1)
end

local function watch_file(filename, callback)
    local last_modified = uv.fs_stat(filename).mtime.sec

    local timer = uv.new_timer()
    timer:start(1000, 1000, function()
        local current_modified = uv.fs_stat(filename).mtime.sec
        if current_modified ~= last_modified then
            callback()
            last_modified = current_modified
        end
    end)

    return timer
end

local function run_lsp_client()
  local stdin_pipe = uv.new_pipe()
  local stdout_pipe = uv.new_pipe()
  local stderr_pipe = uv.new_pipe()

  local stopper = uv.new_timer()

  local file = io.open(pathToLspFiles .. ".ait_lsp_log", "w")
  file:write("")
  file:close();

  local handle, pid = uv.spawn(lspCmd, {
      args = lspArgs,
      stdio = {stdin_pipe, stdout_pipe, stderr_pipe}
    }, function(code, signal)
      print("Process exited with code " .. code .. " and signal " .. signal)
      stdin_pipe:close()
      stdout_pipe:close()
      stderr_pipe:close()
    end)

  if not handle then
    print("Failed to spawn LSP process")
    return
  end

  stderr_pipe:read_start(function(err, data)
    if err then
      print("stderr error:", err)
      return
    end
    if data then
      print("stderr:", data)
    end
  end)

  local function send_request(request)
    local payload = json.encode(request)
    local request_str = string.format("Content-Length: %d\r\n\r\n%s", #payload, payload)
    stdin_pipe:write(request_str)
  end


  local function lsp_cmd(vars)
    local filePath
    local line
    local col
    local command
    if #vars > 3 then
      command = vars[1]
      filePath = vars[2]
      line = tonumber(vars[3]) - 1
      col = tonumber(vars[4]) - 1
    else
      command = "c"
      filePath = vars[1]
      line = tonumber(vars[2]) - 1
      col = tonumber(vars[3]) - 1
    end
    local fp = io.open(filePath)
    local contents = fp:read("*all");
    fp:close()
    local didOpen_request = {
      jsonrpc = "2.0",
      method = "textDocument/didOpen",
      params = {
        textDocument = {
          uri = "file://" .. filePath,
          languageId = langId,
          version = 1,
          text = contents
        }
      }
    }
    send_request(didOpen_request)

    local documentSymbol_request = {
      jsonrpc = "2.0",
      method = "textDocument/documentSymbol",
      id = 4,
      params = {
        textDocument = {
          uri = "file://" .. filePath,
        }
      }
    }
    send_request(documentSymbol_request)

    local request
    if command == "c" then
      request = {
        jsonrpc = "2.0",
        id = 5,
        method = "textDocument/completion",
        params = {
          textDocument = {
            uri = "file://" .. filePath
          },
          position = {
            line = line,
            character = col
          }
        }
      }
    elseif command == "h" then
      request = {
        jsonrpc = "2.0",
        id = 5,
        method = "textDocument/hover",
        params = {
          textDocument = {
            uri = "file://" .. filePath
          },
          position = {
            line = line,
            character = col
          }
        }
      }
    elseif command == "a" then
      local eline = tonumber(vars[4]) - 1
      local ecol = tonumber(vars[5]) - 1
      request = {
        jsonrpc = "2.0",
        id = 5,
        method = "textDocument/codeAction",
        params = {
          textDocument = {
            uri = "file://" .. filePath
          },
          range = {
            start = {
              line = line,
              character = col
            },
            ["end"] = {
              line = eline,
              character = ecol
            }
          },
          context = {
            diagnostics = {
              range = {
                start = {
                  line = line, character = col
                },
                ["end"] = {
                  line = eline, character = ecol
                }
              },
              severity = 1,
            },
            only = { "quickfix", "refactor" },
            triggerKind = 1
          }
        }
      }
    end
    send_request(request)
  end

  local buffer = ""
  stdout_pipe:read_start(function(err, data)
    file = io.open(pathToLspFiles .. ".ait_lsp_log", "a")
    file:write(data .. "\n")
    print(data)
    if err then
      print("stdout error:", err)
     return
    end
    local count = tonumber(data:match("Content%-Length: (%d+)"))
    local js = nil
    local start_index = data:find("{")
    if start_index and count ~= nil then
      js = data:sub(start_index)
      if js:find("result") then
        buffer = js
--       else
--         buffer = ""
      end
    end
    if buffer:match("result") then
      if js == nil then
        buffer = buffer .. data
      end
      local parsed = json.decode(buffer)
      if parsed then
        if parsed.result and parsed.result.items then
          local opts = io.open(pathToLspFiles .. ".ait_lsp_options", "w")
          for _, item in ipairs(parsed.result.items) do
            local str = item.label .. "\t" .. json.encode(item) .. "\n"
            opts:write(str)
          end
          opts:close()
        end
        buffer = ""
      end
    end
    file:close()
  end)

  local timer = uv.new_timer()
  timer:start(10000, 0, function()
    local initialize_request = {
      jsonrpc = "2.0",
      id = 1,
      method = "initialize",
      params = {
        processId = pid,
        rootPath = projectRoot,
        rootUri = "file://" .. projectRoot,
        capabilities = {
          textDocument = {
            completion = {
              completionItem = {
                snippetSupport = true
              }
            }
          },
          hover = {},
          codeAction = {
            dynamicRegistration = true,
            codeActionKinds = {
              "quickfix",
              "refactor",
              "refactor.extract",
              "refactor.inline",
              "refactor.rewrite",
              "source",
              "source.organizeImports"
            }
          }
        }
      }
    }

    send_request(initialize_request)

    local initialized_notification = {
      jsonrpc = "2.0",
      method = "initialized",
      params = {}
    }
    send_request(initialized_notification)

    timer:stop()
  end)

  local timer = watch_file(pathToLspFiles .. ".ait_lsp_cmd", function()
    local f = io.open(pathToLspFiles .. ".ait_lsp_cmd", "r")
    local cmd = f:read("*a")
    f:close()
    local vars = {}
    for word in string.gmatch(cmd, "%S+") do
        table.insert(vars, word)
    end
    lsp_cmd(vars)
  end)

  uv.run()
end

run_lsp_client()
